<!DOCTYPE html>
<!--
Copyright 2016 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->

<link rel="import" href="/tracing/base/math/range.html">
<link rel="import" href="/tracing/base/unit.html">
<link rel="import" href="/tracing/metrics/metric_registry.html">
<link rel="import" href="/tracing/metrics/v8/utils.html">
<link rel="import" href="/tracing/value/histogram.html">

<script>
'use strict';

tr.exportTo('tr.metrics.blink', function() {
  // Maps non-aggregated Blink GC events in timeline to telemetry friendly
  // names.
  const BLINK_NON_AGGREGATED_GC_EVENTS_NAMES_MAP = {
    'BlinkGC.AtomicPauseMarkEpilogue': 'blink-gc-atomic-pause-mark-epilogue',
    'BlinkGC.AtomicPauseMarkPrologue': 'blink-gc-atomic-pause-mark-prologue',
    'BlinkGC.AtomicPauseMarkRoots': 'blink-gc-atomic-pause-mark-roots',
    'BlinkGC.IncrementalMarkingStartMarking': 'blink-gc-incremental-start',
    'BlinkGC.IncrementalMarkingStep': 'blink-gc-incremental-step',
    'BlinkGC.UnifiedMarkingStep': 'blink-gc-unified-marking-by-v8',
    'BlinkGC.CompleteSweep': 'blink-gc-complete-sweep',
    'BlinkGC.LazySweepInIdle': 'blink-gc-sweep-task-foreground',
    'BlinkGC.LazySweepOnAllocation': 'blink-gc-sweep-allocation',
    'BlinkGC.AtomicPauseSweepAndCompact':
        'blink-gc-atomic-pause-sweep-and-compact'
  };

  // Maps non-aggregated Blink GC events in timeline to telemetry friendly
  // names using the new naming scheme. This map contains events that occur
  // only on the main thread.
  const BLINK_NON_AGGREGATED_GC_EVENTS_NEW_NAMES_MAP = {
    'BlinkGC.AtomicPauseMarkEpilogue':
        'blink:gc:main_thread:cycle:full:atomic:mark:epilogue',
    'BlinkGC.AtomicPauseMarkPrologue':
        'blink:gc:main_thread:cycle:full:atomic:mark:prologue',
    'BlinkGC.AtomicPauseMarkRoots':
        'blink:gc:main_thread:cycle:full:atomic:mark:roots',
    'BlinkGC.IncrementalMarkingStartMarking':
        'blink:gc:main_thread:cycle:full:incremental:mark:start',
    'BlinkGC.IncrementalMarkingStep':
        'blink:gc:main_thread:cycle:full:incremental:mark:step',
    'BlinkGC.UnifiedMarkingStep':
        'unified:gc:main_thread:cycle:full:mark:step',
    'BlinkGC.CompleteSweep':
        'blink:gc:main_thread:cycle:full:sweep:complete',
    'BlinkGC.LazySweepInIdle':
        'blink:gc:main_thread:cycle:full:sweep:idle',
    'BlinkGC.LazySweepOnAllocation':
        'blink:gc:main_thread:cycle:full:sweep:on_allocation',
    'BlinkGC.AtomicPauseSweepAndCompact':
        'blink:gc:main_thread:cycle:full:atomic:sweep:compact'
  };

  // All events that should be summed up to 'blink-gc-mark-roots'.
  const BLINK_TOP_GC_ROOTS_MARKING_EVENTS = [
    'BlinkGC.VisitRoots'
  ];

  // All events that should be summed up to
  // 'blink-gc-atomic-pause-mark-transitive-closure'.
  const BLINK_GC_ATOMIC_PAUSE_TRANSITIVE_CLOSURE_EVENTS = [
    'BlinkGC.AtomicPauseMarkTransitiveClosure'
  ];

  // All events that should be summed up to 'blink-gc-mark-transitive-closure'.
  const BLINK_GC_FOREGROUND_MARKING_TRANSITIVE_CLOSURE_EVENTS = [
    'BlinkGC.AtomicPauseMarkTransitiveClosure',
    'BlinkGC.IncrementalMarkingStep',
    'BlinkGC.UnifiedMarkingStep'
  ];

  // Names of Blink GC foreground marking events in timeline.
  const BLINK_TOP_GC_FOREGROUND_MARKING_EVENTS = [
    'BlinkGC.AtomicPauseMarkEpilogue',
    'BlinkGC.AtomicPauseMarkPrologue',
    'BlinkGC.AtomicPauseMarkRoots',
    'BlinkGC.IncrementalMarkingStartMarking',
  ].concat(BLINK_GC_FOREGROUND_MARKING_TRANSITIVE_CLOSURE_EVENTS);

  // Names of Blink GC foreground marking events in timeline.
  const BLINK_GC_FORCED_FOREGROUND_MARKING_EVENTS = [
    'BlinkGC.AtomicPauseMarkEpilogue',
    'BlinkGC.AtomicPauseMarkPrologue',
    'BlinkGC.AtomicPauseMarkRoots',
    'BlinkGC.IncrementalMarkingStartMarking',
    'BlinkGC.MarkBailOutObjects',
    'BlinkGC.MarkFlushV8References',
    'BlinkGC.MarkFlushEphemeronPairs',
  ];

  // Names of Blink GC background marking events in timeline.
  const BLINK_TOP_GC_BACKGROUND_MARKING_EVENTS = [
    'BlinkGC.ConcurrentMarkingStep'
  ];

  // Names of Blink GC foreground sweeping events in timeline.
  const BLINK_TOP_GC_FOREGROUND_SWEEPING_EVENTS = [
    'BlinkGC.CompleteSweep',
    'BlinkGC.LazySweepInIdle',
    'BlinkGC.LazySweepOnAllocation'
  ];

  // Names of Blink GC background sweeping events in timeline.
  const BLINK_TOP_GC_BACKGROUND_SWEEPING_EVENTS = [
    'BlinkGC.ConcurrentSweepingStep'
  ];

  // Names of all Blink Unified GC events in timeline.
  const BLINK_TOP_GC_EVENTS =
      Object.keys(BLINK_NON_AGGREGATED_GC_EVENTS_NAMES_MAP).concat(
          BLINK_GC_ATOMIC_PAUSE_TRANSITIVE_CLOSURE_EVENTS);

  // All events that should be summed up to 'blink-gc-atomic-pause'. Note that
  // this events need to have an epoch counter in args.epoch.
  const ATOMIC_PAUSE_EVENTS = [
    'BlinkGC.AtomicPauseMarkEpilogue',
    'BlinkGC.AtomicPauseMarkPrologue',
    'BlinkGC.AtomicPauseMarkRoots',
    'BlinkGC.AtomicPauseMarkTransitiveClosure',
    'BlinkGC.AtomicPauseSweepAndCompact'
  ];

  function blinkGarbageCollectionEventName(event) {
    return BLINK_NON_AGGREGATED_GC_EVENTS_NAMES_MAP[event.title];
  }

  function blinkGarbageCollectionEventNames() {
    return Object.values(BLINK_NON_AGGREGATED_GC_EVENTS_NAMES_MAP);
  }

  function blinkGarbageCollectionEventNewName(event) {
    return BLINK_NON_AGGREGATED_GC_EVENTS_NEW_NAMES_MAP[event.title];
  }

  function blinkGarbageCollectionEventNewNames() {
    return Object.values(BLINK_NON_AGGREGATED_GC_EVENTS_NEW_NAMES_MAP);
  }

  function isNonForcedEvent(event) {
    return (!event.args || !event.args.forced) &&
           !tr.metrics.v8.utils.isForcedGarbageCollectionEvent(event);
  }

  function isNonForcedBlinkGarbageCollectionEvent(event) {
    return BLINK_TOP_GC_EVENTS.includes(event.title) && isNonForcedEvent(event);
  }

  function isNonForcedNonAggregatedBlinkGarbageCollectionEvent(event) {
    return event.title in BLINK_NON_AGGREGATED_GC_EVENTS_NAMES_MAP &&
           isNonForcedEvent(event);
  }

  function isNonForcedBlinkGarbageCollectionAtomicPauseEvent(event) {
    return ATOMIC_PAUSE_EVENTS.includes(event.title) && isNonForcedEvent(event);
  }

  function isNonForcedBlinkGarbageCollectionRootsMarkingEvent(event) {
    return BLINK_TOP_GC_ROOTS_MARKING_EVENTS.includes(event.title) &&
           isNonForcedEvent(event);
  }

  function
  isNonForcedBlinkGarbageCollectionMarkingTransitiveColsureEvent(event) {
    return BLINK_GC_FOREGROUND_MARKING_TRANSITIVE_CLOSURE_EVENTS.includes(
        event.title) && isNonForcedEvent(event);
  }

  function
  isNonForcedBlinkGarbageCollectionAtomicPauseTransitiveColsureEvent(event) {
    return BLINK_GC_ATOMIC_PAUSE_TRANSITIVE_CLOSURE_EVENTS
        .includes(event.title) && isNonForcedEvent(event);
  }

  function isNonForcedBlinkGarbageCollectionForegroundMarkingEvent(event) {
    return BLINK_TOP_GC_FOREGROUND_MARKING_EVENTS.includes(event.title) &&
           isNonForcedEvent(event);
  }

  function isNonForcedBlinkGarbageCollectionForcedForegroundMarkEvent(event) {
    return BLINK_GC_FORCED_FOREGROUND_MARKING_EVENTS.includes(
        event.title) &&
           isNonForcedEvent(event);
  }

  function isNonForcedBlinkGarbageCollectionBackgroundMarkingEvent(event) {
    return BLINK_TOP_GC_BACKGROUND_MARKING_EVENTS.includes(event.title) &&
           isNonForcedEvent(event);
  }

  function isNonForcedBlinkGarbageCollectionForegroundSweepingEvent(event) {
    return BLINK_TOP_GC_FOREGROUND_SWEEPING_EVENTS.includes(event.title) &&
           isNonForcedEvent(event);
  }

  function isNonForcedBlinkGarbageCollectionBackgroundSweepingEvent(event) {
    return BLINK_TOP_GC_BACKGROUND_SWEEPING_EVENTS.includes(event.title) &&
           isNonForcedEvent(event);
  }

  function isNonNestedNonForcedBlinkGarbageCollectionEvent(event) {
    return isNonForcedBlinkGarbageCollectionEvent(event) &&
           !tr.metrics.v8.utils.findParent(event,
               tr.metrics.v8.utils.isGarbageCollectionEvent);
  }

  function blinkGcMetric(histograms, model) {
    addDurationOfTopEvents(histograms, model);
    addDurationOfAtomicPause(histograms, model);
    addDurationOfAtomicPauseTransitiveClosure(histograms, model);
    addTotalDurationOfTopEvents(histograms, model);
    addTotalDurationOfBlinkAndV8TopEvents(histograms, model);
    addTotalDurationOfRootsMarking(histograms, model);
    addTotalDurationOfMarkingTransitiveClosure(histograms, model);
    addTotalDurationOfForegroundMarking(histograms, model);
    addTotalDurationOfForcedForegroundMarking(histograms, model);
    addTotalDurationOfBackgroundMarking(histograms, model);
    addTotalDurationOfForegroundSweeping(histograms, model);
    addTotalDurationOfBackgroundSweeping(histograms, model);
  }

  function getEventEpochUniqueId(event) {
    // event.parentContainer.parent.stableId return the event's process id.
    return event.parentContainer.parent.stableId + ':' + event.args.epoch;
  }

  tr.metrics.MetricRegistry.register(blinkGcMetric);

  // 0.1 steps from 0 to 20 since it is the most common range.
  // Exponentially increasing steps from 20 to 200.
  const CUSTOM_BOUNDARIES = tr.v.HistogramBinBoundaries.createLinear(0, 20, 200)
      .addExponentialBins(200, 100);

  function createNumericForEventTime(name) {
    const n = new tr.v.Histogram(name,
        tr.b.Unit.byName.timeDurationInMs_smallerIsBetter, CUSTOM_BOUNDARIES);
    n.customizeSummaryOptions({
      avg: true,
      count: true,
      max: true,
      min: true,
      std: true,
      sum: true,
      percentile: [0.90]});
    return n;
  }

  /**
   * Example output:
   * - blink-gc-incremental-start
   */
  function addDurationOfTopEvents(histograms, model) {
    const nameToNumeric = {};
    const nameToEpochNumeric = {};
    for (const name of blinkGarbageCollectionEventNames()) {
      nameToNumeric[name] = createNumericForEventTime(name);
    }
    for (const name of blinkGarbageCollectionEventNewNames()) {
      nameToEpochNumeric[name] = createNumericForEventTime(name);
    }
    tr.metrics.v8.utils.groupAndProcessEvents(model,
        isNonForcedNonAggregatedBlinkGarbageCollectionEvent,
        getEventEpochUniqueId,
        function(epoch, events) {
          const namesToPerEpochDurations = {};
          for (const event of events) {
            nameToNumeric[blinkGarbageCollectionEventName(event)]
                .addSample(event.cpuDuration);
            const name = blinkGarbageCollectionEventNewName(event);
            namesToPerEpochDurations[name] =
                (namesToPerEpochDurations[name] || 0) + event.cpuDuration;
          }
          for (const name in namesToPerEpochDurations) {
            nameToEpochNumeric[name].addSample(namesToPerEpochDurations[name]);
          }
        }
    );
    for (const name of blinkGarbageCollectionEventNames()) {
      histograms.addHistogram(nameToNumeric[name]);
    }
    for (const name of blinkGarbageCollectionEventNewNames()) {
      histograms.addHistogram(nameToEpochNumeric[name]);
    }
  }

  function addIndividualDurationsOfEvents(histograms, model, name, filter) {
    const cpuDuration = createNumericForEventTime(name);
    tr.metrics.v8.utils.groupAndProcessEvents(model,
        filter,
        event => name,
        function(group, events) {
          for (const event of events) {
            cpuDuration.addSample(event.cpuDuration);
          }
        },
        [name]
    );
    histograms.addHistogram(cpuDuration);
  }

  function addPerEpochDurationsOfEvents(histograms, model, name, filter) {
    const cpuDuration = createNumericForEventTime(name);
    tr.metrics.v8.utils.groupAndProcessEvents(model,
        filter,
        getEventEpochUniqueId,
        function(epoch, events) {
          cpuDuration.addSample(
              events.reduce((acc, current) => acc + current.cpuDuration, 0));
        }
    );
    histograms.addHistogram(cpuDuration);
  }

  /**
   * Example output:
   * - blink-gc-atomic-pause
   */
  function addDurationOfAtomicPause(histograms, model) {
    addIndividualDurationsOfEvents(histograms, model,
        'blink-gc-atomic-pause',
        isNonForcedBlinkGarbageCollectionAtomicPauseEvent);
    addPerEpochDurationsOfEvents(histograms, model,
        'blink:gc:main_thread:cycle:full:atomic',
        isNonForcedBlinkGarbageCollectionAtomicPauseEvent);
  }

  /**
   * Example output:
   * - blink-gc-atomic-pause-mark-transitive-closure
   */
  function addDurationOfAtomicPauseTransitiveClosure(histograms, model) {
    addIndividualDurationsOfEvents(histograms, model,
        'blink-gc-atomic-pause-mark-transitive-closure',
        isNonForcedBlinkGarbageCollectionAtomicPauseTransitiveColsureEvent);
    addPerEpochDurationsOfEvents(histograms, model,
        'blink:gc:main_thread:cycle:full:atomic:mark:transitive_closure',
        isNonForcedBlinkGarbageCollectionAtomicPauseTransitiveColsureEvent);
  }

  /**
   * Example output:
   * - blink-gc-total
   */
  function addTotalDurationOfTopEvents(histograms, model) {
    addIndividualDurationsOfEvents(histograms, model,
        'blink-gc-total',
        isNonForcedBlinkGarbageCollectionEvent);
    addPerEpochDurationsOfEvents(histograms, model,
        'blink:gc:main_thread:cycle:full',
        isNonForcedBlinkGarbageCollectionEvent);
  }

  /**
   * Example output:
   * - blink-gc-mark-roots
   */
  function addTotalDurationOfRootsMarking(histograms, model) {
    addIndividualDurationsOfEvents(histograms, model,
        'blink-gc-mark-roots',
        isNonForcedBlinkGarbageCollectionRootsMarkingEvent);
    addPerEpochDurationsOfEvents(histograms, model,
        'blink:gc:main_thread:cycle:full:mark:roots',
        isNonForcedBlinkGarbageCollectionRootsMarkingEvent);
  }

  /**
   * Example output:
   * - blink-gc-mark-transitive-closure
   */
  function addTotalDurationOfMarkingTransitiveClosure(histograms, model) {
    addIndividualDurationsOfEvents(histograms, model,
        'blink-gc-mark-transitive-closure',
        isNonForcedBlinkGarbageCollectionMarkingTransitiveColsureEvent);
    addPerEpochDurationsOfEvents(histograms, model,
        'blink:gc:main_thread:cycle:full:mark:transitive_closure',
        isNonForcedBlinkGarbageCollectionMarkingTransitiveColsureEvent);
  }

  /**
   * Example output:
   * - blink-gc-mark-foreground
   */
  function addTotalDurationOfForegroundMarking(histograms, model) {
    addIndividualDurationsOfEvents(histograms, model,
        'blink-gc-mark-foreground',
        isNonForcedBlinkGarbageCollectionForegroundMarkingEvent);
    addPerEpochDurationsOfEvents(histograms, model,
        'blink:gc:main_thread:cycle:full:mark',
        isNonForcedBlinkGarbageCollectionForegroundMarkingEvent);
  }

  /**
   * Example output:
   * - blink-gc-mark-foreground-forced
   */
  function addTotalDurationOfForcedForegroundMarking(histograms, model) {
    addIndividualDurationsOfEvents(histograms, model,
        'blink-gc-mark-foreground-forced',
        isNonForcedBlinkGarbageCollectionForcedForegroundMarkEvent);
    addPerEpochDurationsOfEvents(histograms, model,
        'blink:gc:main_thread:cycle:full:mark:forced',
        isNonForcedBlinkGarbageCollectionForcedForegroundMarkEvent);
  }

  /**
   * Example output:
   * - blink-gc-mark-background
   */
  function addTotalDurationOfBackgroundMarking(histograms, model) {
    addIndividualDurationsOfEvents(histograms, model,
        'blink-gc-mark-background',
        isNonForcedBlinkGarbageCollectionBackgroundMarkingEvent);
    addPerEpochDurationsOfEvents(histograms, model,
        'blink:gc:concurrent_thread:cycle:full:mark',
        isNonForcedBlinkGarbageCollectionBackgroundMarkingEvent);
  }

  /**
   * Example output:
   * - blink-gc-sweep-foreground
   */
  function addTotalDurationOfForegroundSweeping(histograms, model) {
    addIndividualDurationsOfEvents(histograms, model,
        'blink-gc-sweep-foreground',
        isNonForcedBlinkGarbageCollectionForegroundSweepingEvent);
    addPerEpochDurationsOfEvents(histograms, model,
        'blink:gc:main_thread:cycle:full:sweep',
        isNonForcedBlinkGarbageCollectionForegroundSweepingEvent);
  }

  /**
   * Example output:
   * - blink-gc-sweep-background
   */
  function addTotalDurationOfBackgroundSweeping(histograms, model) {
    addIndividualDurationsOfEvents(histograms, model,
        'blink-gc-sweep-background',
        isNonForcedBlinkGarbageCollectionBackgroundSweepingEvent);
    addPerEpochDurationsOfEvents(histograms, model,
        'blink:gc:concurrent_thread:cycle:full:sweep',
        isNonForcedBlinkGarbageCollectionBackgroundSweepingEvent);
  }

  function isV8OrBlinkTopLevelGarbageCollectionEvent(event) {
    return tr.metrics.v8.utils.isNotForcedTopGarbageCollectionEvent(event) ||
           isNonNestedNonForcedBlinkGarbageCollectionEvent(event);
  }

  /**
   * Example output:
   * - unified-gc-total
   */
  function addTotalDurationOfBlinkAndV8TopEvents(histograms, model) {
    addIndividualDurationsOfEvents(histograms, model,
        'unified-gc-total',
        isV8OrBlinkTopLevelGarbageCollectionEvent);
    // No epoch durations because v8 events don't provide an epoch id.
  }

  return {
    blinkGcMetric,
  };
});
</script>
